import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';

typedef _SpeexEncoderInit = Pointer<Void> Function(Int32 quality, Int32 sampleRate);
typedef _SpeexEncoderInitDart = Pointer<Void> Function(int quality, int sampleRate);

typedef _SpeexEncodeFrame = Int32 Function(Pointer<Void> encoder, Pointer<Int16> pcm, Pointer<Uint8> output, Int32 maxLen);
typedef _SpeexEncodeFrameDart = int Function(Pointer<Void> encoder, Pointer<Int16> pcm, Pointer<Uint8> output, int maxLen);

typedef _SpeexGetFrameSize = Int32 Function(Pointer<Void> encoder);
typedef _SpeexGetFrameSizeDart = int Function(Pointer<Void> encoder);

typedef _SpeexDestroy = Void Function(Pointer<Void> encoder);
typedef _SpeexDestroyDart = void Function(Pointer<Void> encoder);

class SpeexEncoderFFI {
  late DynamicLibrary _lib;
  late _SpeexEncoderInitDart _init;
  late _SpeexEncodeFrameDart _encode;
  late _SpeexGetFrameSizeDart _getFrameSize;
  late _SpeexDestroyDart _destroy;

  Pointer<Void>? _encoder;

  SpeexEncoderFFI() {
    if(Platform.isAndroid) {
      _lib = DynamicLibrary.open('libspeex.so'); // Android
    } else {
      _lib = DynamicLibrary.process();
    }
    _init = _lib.lookupFunction<_SpeexEncoderInit, _SpeexEncoderInitDart>('speex_encoder_init_wrapper');
    _encode = _lib.lookupFunction<_SpeexEncodeFrame, _SpeexEncodeFrameDart>('speex_encode_frame');
    _getFrameSize = _lib.lookupFunction<_SpeexGetFrameSize, _SpeexGetFrameSizeDart>('speex_get_frame_size');
    _destroy = _lib.lookupFunction<_SpeexDestroy, _SpeexDestroyDart>('speex_encoder_destroy');

    _encoder = _init(7, 16000); // quality=7, sampleRate=16kHz
  }

  int get frameSize => _getFrameSize(_encoder!);

  Uint8List encode(Uint8List pcmData) {
    final frameCount = pcmData.length ~/ 2; // PCM 16bit
    final pcmPtr = malloc.allocate<Int16>(frameCount * sizeOf<Int16>());
    final byteData = pcmData.buffer.asByteData();
    for (var i = 0; i < frameCount; i++) {
      pcmPtr[i] = byteData.getInt16(i * 2, Endian.little);
    }

    const maxLen = 200; // 够用的缓存
    final outputPtr = malloc.allocate<Uint8>(maxLen);

    final len = _encode(_encoder!, pcmPtr, outputPtr, maxLen);

    final encoded = outputPtr.asTypedList(len);
    final copyData = Uint8List.fromList(encoded);
    malloc.free(pcmPtr);
    malloc.free(outputPtr);
    return copyData;
  }

  void dispose() {
    _destroy(_encoder!);
    _encoder = null;
  }
}

class SpeexDecoderFFI {
  late DynamicLibrary _lib;

  late Pointer<Void> Function() _init;
  late int Function(Pointer<Void> decoder, Pointer<Int8> input, int len, Pointer<Int16> output) _decode;
  late int Function(Pointer<Void> decoder) _getFrameSize;
  late void Function(Pointer<Void> decoder) _destroy;

  Pointer<Void>? _decoder;

  SpeexDecoderFFI() {
    if(Platform.isAndroid) {
      _lib = DynamicLibrary.open('libspeex.so'); // Android
    } else {
      _lib = DynamicLibrary.process();
    }
    _init = _lib.lookupFunction<Pointer<Void> Function(), Pointer<Void> Function()>('speex_decoder_init_wrapper');
    _decode = _lib.lookupFunction<
        Int32 Function(Pointer<Void>, Pointer<Int8>, Int32, Pointer<Int16>),
        int Function(Pointer<Void>, Pointer<Int8>, int, Pointer<Int16>)
    >('speex_decode_frame_wrapper');

    _getFrameSize = _lib.lookupFunction<Int32 Function(Pointer<Void>), int Function(Pointer<Void>)>('speex_get_frame_size_decoder');
    _destroy = _lib.lookupFunction<Void Function(Pointer<Void>), void Function(Pointer<Void>)>('speex_decoder_destroy_wrapper');

    _decoder = _init();
  }

  int get frameSize => _getFrameSize(_decoder!);

  /// 解码一帧 Speex 裸流数据
  Uint8List decode(Uint8List speexFrame) {
    final pcmPtr = malloc.allocate<Int16>(frameSize * sizeOf<Int16>());
    final inputPtr = malloc.allocate<Int8>(speexFrame.length);
    inputPtr.asTypedList(speexFrame.length).setAll(0, speexFrame);


    final ret = _decode(_decoder!, inputPtr, speexFrame.length, pcmPtr);

    Uint8List pcmBytes = Uint8List(frameSize * 2);
    final byteData = pcmBytes.buffer.asByteData();
    for (var i = 0; i < frameSize; i++) {
      byteData.setInt16(i * 2, pcmPtr[i], Endian.little);
    }

    malloc.free(pcmPtr);
    malloc.free(inputPtr);

    if (ret < 0) {
      // 解码失败
      return Uint8List(0);
    }
    return pcmBytes;
  }


  void dispose() {
    _destroy(_decoder!);
    _decoder = null;
  }
}